﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LogTracerPlugin.cs" company="">
// </copyright>
// <summary>
//   The log tracer plugin.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace LogTracer.SpecflowPlugin
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;

    using TechTalk.SpecFlow;
    using TechTalk.SpecFlow.Bindings;
    using TechTalk.SpecFlow.Bindings.Reflection;
    using TechTalk.SpecFlow.BindingSkeletons;
    using TechTalk.SpecFlow.Configuration;
    using TechTalk.SpecFlow.Plugins;
    using TechTalk.SpecFlow.Tracing;

    /// <summary>
    ///     The log tracer plugin.
    /// </summary>
    public class LogTracerPlugin : IRuntimePlugin
    {
        #region Public Methods and Operators

        /// <summary>
        /// The initialize.
        /// </summary>
        /// <param name="runtimePluginEvents">
        /// The runtime plugin events.
        /// </param>
        /// <param name="runtimePluginParameters">
        /// The runtime plugin parameters.
        /// </param>
        public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters)
        {
            runtimePluginEvents.CustomizeGlobalDependencies += this.RuntimePluginEventsOnCustomizeGlobalDependencies;
        }

        #endregion

        #region Methods

        private void RuntimePluginEventsOnCustomizeGlobalDependencies(
            object sender, 
            CustomizeGlobalDependenciesEventArgs customizeGlobalDependenciesEventArgs)
        {
            customizeGlobalDependenciesEventArgs.ObjectContainer.RegisterTypeAs<LogTracer, ITestTracer>();
        }

        #endregion
    }

    /// <summary>
    ///     The log tracer.
    /// </summary>
    public class LogTracer : ITestTracer
    {
        #region Fields

        /// <summary>
        ///     The runtime configuration.
        /// </summary>
        private readonly RuntimeConfiguration runtimeConfiguration;

        /// <summary>
        ///     The step definition skeleton provider.
        /// </summary>
        private readonly IStepDefinitionSkeletonProvider stepDefinitionSkeletonProvider;

        /// <summary>
        ///     The step formatter.
        /// </summary>
        private readonly IStepFormatter stepFormatter;

        /// <summary>
        ///     The trace listener.
        /// </summary>
        private readonly ITraceListener traceListener;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="LogTracer"/> class.
        /// </summary>
        /// <param name="traceListener">
        /// The trace listener.
        /// </param>
        /// <param name="stepFormatter">
        /// The step formatter.
        /// </param>
        /// <param name="stepDefinitionSkeletonProvider">
        /// The step definition skeleton provider.
        /// </param>
        /// <param name="runtimeConfiguration">
        /// The runtime configuration.
        /// </param>
        public LogTracer(
            ITraceListener traceListener, 
            IStepFormatter stepFormatter, 
            IStepDefinitionSkeletonProvider stepDefinitionSkeletonProvider, 
            RuntimeConfiguration runtimeConfiguration)
        {
            this.traceListener = traceListener;
            this.stepFormatter = stepFormatter;
            this.stepDefinitionSkeletonProvider = stepDefinitionSkeletonProvider;
            this.runtimeConfiguration = runtimeConfiguration;
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The trace binding error.
        /// </summary>
        /// <param name="ex">
        /// The ex.
        /// </param>
        public void TraceBindingError(BindingException ex)
        {
            this.traceListener.WriteToolOutput("binding error: {0}", ex.Message);
        }

        /// <summary>
        /// The trace duration.
        /// </summary>
        /// <param name="elapsed">
        /// The elapsed.
        /// </param>
        /// <param name="method">
        /// The method.
        /// </param>
        /// <param name="arguments">
        /// The arguments.
        /// </param>
        public void TraceDuration(TimeSpan elapsed, IBindingMethod method, object[] arguments)
        {
            this.traceListener.WriteToolOutput(
                "duration: {0}: {1:F1}s", 
                this.stepFormatter.GetMatchText(method, arguments), 
                elapsed.TotalSeconds);
        }

        /// <summary>
        /// The trace duration.
        /// </summary>
        /// <param name="elapsed">
        /// The elapsed.
        /// </param>
        /// <param name="text">
        /// The text.
        /// </param>
        public void TraceDuration(TimeSpan elapsed, string text)
        {
            this.traceListener.WriteToolOutput("duration: {0}: {1:F1}s", text, elapsed.TotalSeconds);
        }

        /// <summary>
        /// The trace error.
        /// </summary>
        /// <param name="ex">
        /// The ex.
        /// </param>
        public void TraceError(Exception ex)
        {
            this.traceListener.WriteToolOutput("error: {0}", ex.Message);
        }

        /// <summary>
        /// The trace no matching step definition.
        /// </summary>
        /// <param name="stepInstance">
        /// The step instance.
        /// </param>
        /// <param name="targetLanguage">
        /// The target language.
        /// </param>
        /// <param name="bindingCulture">
        /// The binding culture.
        /// </param>
        /// <param name="matchesWithoutScopeCheck">
        /// The matches without scope check.
        /// </param>
        public void TraceNoMatchingStepDefinition(
            StepInstance stepInstance, 
            ProgrammingLanguage targetLanguage, 
            CultureInfo bindingCulture, 
            List<BindingMatch> matchesWithoutScopeCheck)
        {
            var message = new StringBuilder();
            if (matchesWithoutScopeCheck == null || matchesWithoutScopeCheck.Count == 0)
            {
                message.AppendLine(
                    "No matching step definition found for the step. Use the following code to create one:");
            }
            else
            {
                string preMessage =
                    string.Format(
                        "No matching step definition found for the step. There are matching step definitions, but none of them have matching scope for this step: {0}.", 
                        string.Join(
                            ", ", 
                            matchesWithoutScopeCheck.Select(m => this.stepFormatter.GetMatchText(m, null)).ToArray()));
                this.traceListener.WriteToolOutput(preMessage);
                message.AppendLine("Change the scope or use the following code to create a new step definition:");
            }

            message.Append(
                this.stepDefinitionSkeletonProvider.GetStepDefinitionSkeleton(
                    targetLanguage, 
                    stepInstance, 
                    this.runtimeConfiguration.StepDefinitionSkeletonStyle, 
                    bindingCulture).Indent(StepDefinitionSkeletonProvider.METHOD_INDENT));

            this.traceListener.WriteToolOutput(message.ToString());
        }

        /// <summary>
        /// The trace step.
        /// </summary>
        /// <param name="stepInstance">
        /// The step instance.
        /// </param>
        /// <param name="showAdditionalArguments">
        /// The show additional arguments.
        /// </param>
        public void TraceStep(StepInstance stepInstance, bool showAdditionalArguments)
        {
            string stepText = this.stepFormatter.GetStepText(stepInstance);
            this.traceListener.WriteTestOutput(stepText.TrimEnd());

            // Custom Message
            Console.WriteLine("Hello: " + stepInstance.Text);
        }

        /// <summary>
        /// The trace step done.
        /// </summary>
        /// <param name="match">
        /// The match.
        /// </param>
        /// <param name="arguments">
        /// The arguments.
        /// </param>
        /// <param name="duration">
        /// The duration.
        /// </param>
        public void TraceStepDone(BindingMatch match, object[] arguments, TimeSpan duration)
        {
            this.traceListener.WriteToolOutput(
                "done: {0} ({1:F1}s)", 
                this.stepFormatter.GetMatchText(match, arguments), 
                duration.TotalSeconds);
        }

        /// <summary>
        /// The trace step pending.
        /// </summary>
        /// <param name="match">
        /// The match.
        /// </param>
        /// <param name="arguments">
        /// The arguments.
        /// </param>
        public void TraceStepPending(BindingMatch match, object[] arguments)
        {
            this.traceListener.WriteToolOutput("pending: {0}", this.stepFormatter.GetMatchText(match, arguments));
        }

        /// <summary>
        ///     The trace step skipped.
        /// </summary>
        public void TraceStepSkipped()
        {
            this.traceListener.WriteToolOutput("skipped because of previous errors");
        }

        /// <summary>
        /// The trace warning.
        /// </summary>
        /// <param name="text">
        /// The text.
        /// </param>
        public void TraceWarning(string text)
        {
            this.traceListener.WriteToolOutput("warning: {0}", text);
        }

        #endregion
    }
}